﻿using System;
using System.Reflection.Emit;
using System.Reflection;

namespace Rsdn.SmartApp
{
	partial class ServicesHelper
	{
		private static class ReflectedPublicator
		{
			private static readonly MethodInfo _publishInstanceMethod;
			private static readonly MethodInfo _publishCreatorMethod;
			private static readonly ConstructorInfo _helperCtor;
			private static readonly MethodInfo _createHelperMethod;
			private static readonly ElementsCache<Type, CallCreatorPublish> _wrappedCreatorMethods =
				new ElementsCache<Type, CallCreatorPublish>(CreateCreatorWrapper);
			private static readonly ElementsCache<Type, CallInstancePublish> _wrappedInstanceMethods =
				new ElementsCache<Type, CallInstancePublish>(CreateInstanceWrapper);

			static ReflectedPublicator()
			{
				Type pt = typeof (IServicePublisher);
				foreach (MethodInfo mi in pt.GetMethods())
				{
					if (mi.Name == "Publish" && mi.GetParameters()[0].Name == "serviceInstance")
						_publishInstanceMethod = mi;
					if (mi.Name == "Publish" && mi.GetParameters()[0].Name == "serviceCreator")
						_publishCreatorMethod = mi;
				}
				Type helperType = typeof (CreatorHelper);
				_helperCtor = helperType.GetConstructor(
					new Type[] {typeof (ServiceCreator<object>)});
				_createHelperMethod = helperType.GetMethod("CreateService");
			}

			/// <summary>
			/// Публикует экземпляр сервиса.
			/// </summary>
			public static IServiceCookie Publish(IServicePublisher publisher, Type serviceType,
				object serviceInstance)
			{
				return _wrappedInstanceMethods.Get(serviceType)(publisher, serviceInstance);
			}

			private static CallInstancePublish CreateInstanceWrapper(Type serviceType)
			{
				var method = new DynamicMethod("InstanceWrapper" + serviceType.FullName,
					typeof(IServiceCookie),
					new Type[] {
					typeof (IServicePublisher),
					typeof (object)},
					typeof(ReflectedPublicator),
					true);

				ILGenerator ilGen = method.GetILGenerator();
				ilGen.Emit(OpCodes.Ldarg_0);
				ilGen.Emit(OpCodes.Ldarg_1);
				ilGen.Emit(OpCodes.Castclass, serviceType);
				ilGen.Emit(OpCodes.Callvirt, _publishInstanceMethod.MakeGenericMethod(serviceType));
				ilGen.Emit(OpCodes.Ret);

				return (CallInstancePublish)method.CreateDelegate(typeof (CallInstancePublish));
			}

			/// <summary>
			/// Публикует сервис с отложенной инициализацией.
			/// </summary>
			public static IServiceCookie Publish(IServicePublisher publisher, Type serviceType,
				ServiceCreator<object> serviceCreator)
			{
				return _wrappedCreatorMethods.Get(serviceType)(publisher, serviceCreator);
			}

			private static CallCreatorPublish CreateCreatorWrapper(Type serviceType)
			{
				CallCreatorPublish ccp;
				var typedDelegate = typeof(ServiceCreator<>).MakeGenericType(serviceType);
				var method = new DynamicMethod("CreatorWrapper" + serviceType.FullName,
					typeof(IServiceCookie),
					new Type[] {
					typeof (IServicePublisher),
					typeof (ServiceCreator<object>)},
					typeof(ReflectedPublicator));
				ILGenerator ilGen = method.GetILGenerator();

				ilGen.Emit(OpCodes.Ldarg_0);
				ilGen.Emit(OpCodes.Ldarg_1);
				ilGen.Emit(OpCodes.Newobj, _helperCtor);
				ilGen.Emit(OpCodes.Ldftn, _createHelperMethod.MakeGenericMethod(serviceType));
				ilGen.Emit(OpCodes.Newobj,
					typedDelegate.GetConstructor(new Type[] { typeof(object), typeof(IntPtr) }));
				ilGen.Emit(OpCodes.Callvirt,
					_publishCreatorMethod.MakeGenericMethod(serviceType));
				ilGen.Emit(OpCodes.Ret);

				ccp = (CallCreatorPublish)method.CreateDelegate(typeof(CallCreatorPublish));
				return ccp;
			}

			private delegate IServiceCookie CallInstancePublish(IServicePublisher publisher,
				object instance);

			private delegate IServiceCookie CallCreatorPublish(IServicePublisher publisher,
				ServiceCreator<object> creator);

			#region CreatorHelper class
			private class CreatorHelper
			{
				private readonly ServiceCreator<object> _serviceCreator;

				public CreatorHelper(ServiceCreator<object> serviceCreator)
				{
					_serviceCreator = serviceCreator;
				}

				public T CreateService<T>()
				{
					return (T)_serviceCreator();
				}
			}
			#endregion
		}
	}
}
